package pzc.jdbc;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Map.Entry;

import pzc.lang.CommonUtils;

/**
 * データベースの共通操作クラス.
 * <p>
 * Select、Insert、Update、Deleteなどの操作
 *
 */
public class DbOperator {

    /** Logger. */
//    private static final Logger logger = Logger.getLogger(DbOperator.class.getName());

    /** コネクション. */
    private Connection conn = null;

    /** 名前付きパラメータ対応PreparedStatement. */
    private NamedPreparedStatement anps = null;

    /**
     * コンストラクタ(デフォルト).
     * <p>
     * コンストラクタ(デフォルト、privateにする)
     *
     */
    @SuppressWarnings("unused")
    private DbOperator() {
    }

    /**
     * コンストラクタ.
     * <p>
     * コンストラクタ.
     *
     * @param conn
     *            コネクション
     * @throws SQLException
     *             SQL例外
     */
    public DbOperator(Connection conn) throws SQLException {
        this.conn = conn;
    }

    /**
     * SQL Logタイトルを設定.
     * @param stmt NamedPreparedStatement
     */
    private void setSqlLogTitle(NamedPreparedStatement stmt) {
    	String thisClassName = getClass().getName();
        Throwable t = new Throwable();
        StackTraceElement st[] = t.getStackTrace();
        for (int i = 2; i < st.length; ++i) {
    		String className = st[i].getClassName();
        	if (!className.equals(thisClassName)) {
        		StringBuffer sb = new StringBuffer();
        		sb.append("SQL:")
        		  .append(className.substring(className.lastIndexOf('.') + 1))
        		  .append('.').append(st[i].getMethodName());
        		stmt.setLogTitle(sb.toString());
	        	return;
	        }
        }
    }
    
    /**
     * SQLの実行（照会-ResultSetを返す）
     * <p>
     * ResultSetは自分で処理できるように用意するメソッドです。処理が終わったら、ResultSetのクローズは忘れないよう。
     *
     * @param srcSql
     *            ソースSQL（{xxCd}、{1}等）付き可)
     * @param paramMap
     *            パラメータが保存しているMap（ヌル可）
     * @return ResultSet
     * @throws SQLException
     *             SQL例外
     */
    public ResultSet executeQuery(String srcSql, Map<String, Object> paramMap)
            throws SQLException {

        this.anps = new NamedPreparedStatement(this.conn, srcSql);
        setSqlLogTitle(this.anps);

        this.setParameterToNamedPreparedStatement(paramMap);

        // SQLを実行する
        return anps.executeQuery();
    }

    /**
     * SQLの実行（照会、自動的にResultSetを繰り返す操作）
     * 
     * @see ResultSetWhileOp#whileRsNext(ResultSet)
     *
     * @param srcSql
     *            ソースSQL（{xxCd}、{1}等）付き可)
     * @param paramMap
     *            パラメータが保存しているMap（ヌル可）
     * @param whileOp
     *            繰り返す操作対象
     * @throws Exception
     *             例外
     */
	public void executeQuery(String srcSql, Map<String, Object> paramMap,
			ResultSetWhileOp whileOp) throws Exception {

		ResultSet rs = this.executeQuery(srcSql, paramMap);

		if (!CommonUtils.isEmpty(rs)) {
			while (rs.next()) {
				whileOp.whileRsNext(rs);
			}
		}

        // ResultSetをクローズする
        rs.close();

        // ステートメントをクローズする
        this.closeStatement();
	}

	/**
     * SQLの実行（照会、ResultSetに結果があれば一回だけ処理する）
     *
     * @see ResultSetIfOp#ifRsNext(ResultSet)
     *
	 * @param srcSql
     *            ソースSQL（{xxCd}、{1}等）付き可)
	 * @param paramMap
     *            パラメータが保存しているMap（ヌル可）
	 * @param ifOp
     *            操作対象
	 * @return 返す結果
	 * @throws Exception
     *             例外
	 */
	public <T> T executeQuery(String srcSql, Map<String, Object> paramMap,
			ResultSetIfOp<T> ifOp) throws Exception {

		T ret = null;

		ResultSet rs = this.executeQuery(srcSql, paramMap);

		if (!CommonUtils.isEmpty(rs)) {
			if (rs.next()) {
				ret = ifOp.ifRsNext(rs);
			}
		}

        // ResultSetをクローズする
        rs.close();

        // ステートメントをクローズする
        this.closeStatement();
        
        return ret;
	}

    /**
     * SQLの実行（更新-実行後コミットしない）.
     * <p>
     * SQLの実行（更新-実行後コミットしない）
     *
     * @return 更新数
     * @throws SQLException
     *             SQL例外
     */
    public int executeUpdate(String srcSql, Map<String, Object> paramMap)
            throws SQLException {

        // 更新数
        int ret = 0;

        try {

            this.anps = new NamedPreparedStatement(this.conn, srcSql);
            setSqlLogTitle(this.anps);

            this.setParameterToNamedPreparedStatement(paramMap);

            // SQLを実行する
            ret = this.anps.executeUpdate();
        } catch (SQLException e) {

            // エラーが発生したら、トランザクションをロールバックする
            // this.conn.rollback();
            throw e;
        } finally {

            // ステートメントをクローズする
            this.closeStatement();
        }

        return ret;
    }

    /**
     * 渡されたSQLにはレコードがあるか判断する.
     * 
     * @param srcSql
     *            ソースSQL
     * @param paramMap
     *            パラメータが保存しているMap(ヌル可)
     * @return 判断結果(true:ある、false:なし)
     * @throws SQLException
     */
    public boolean isHasRecord(String srcSql,
            Map<String, Object> paramMap) throws SQLException {

    	boolean ret = false;

        // SQLを実行する
        ResultSet rs = this.executeQuery(srcSql, paramMap);

        if (rs.next()) {
            ret = true;
        }

        // ResultSetをクローズする
        rs.close();

        // ステートメントをクローズする
        this.closeStatement();

        return ret;
    }

    /**
     * パラメータの設定.
     * <p>
     * パラメータの設定（渡されたMap中のパラメータをNamedPreparedStatementにセットする）
     *
     * @param paramMap
     *            パラメータが保存しているMap(ヌル可)
     * @throws SQLException
     *             SQL例外
     */
    private void setParameterToNamedPreparedStatement(
            Map<String, Object> paramMap) throws SQLException {

        if (paramMap != null) {
            for (Entry<String, Object> paramEntry : paramMap.entrySet()) {
                String key = paramEntry.getKey();
                Object value = paramEntry.getValue();
                if (value instanceof Integer) {
                    this.anps.setIntAtName(key, ((Integer) value).intValue());
                } else if (value instanceof Long) {
                    this.anps.setLongAtName(key, ((Long) value).longValue());
                } else if (value instanceof BigDecimal) {
                    this.anps.setBigDecimalAtName(key, (BigDecimal) value);
                } else if (value instanceof String) {
                    this.anps.setStringAtName(key, (String) value);
//                } else if (value instanceof Bits) {
//                    this.anps.setStringAtName(key, value.toString());
//                } else if (value instanceof PseudoArray) {
//                    this.anps.setStringAtName(key, value.toString());
                } else {
                    this.anps.setObjectAtName(key, value);
                }
            }
        }
    }

    /**
     * ステートメントをクローズする.
     * <p>
     * ステートメントをクローズする
     *
     * @throws SQLException
     */
    public void closeStatement() throws SQLException {
        if (this.anps != null) {
            this.anps.close();
            this.anps = null;
        }
    }
}
